package com.atguigu.gulimall.order.service.impl;

import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.TypeReference;
import com.alipay.api.AlipayApiException;
import com.alipay.api.internal.util.AlipaySignature;
import com.atguigu.common.exception.NoStockException;
import com.atguigu.common.to.mq.OrderTo;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;
import com.atguigu.common.utils.R;
import com.atguigu.common.vo.MemberRespVo;
import com.atguigu.gulimall.order.config.AlipayTemplate;
import com.atguigu.gulimall.order.constant.OrderConstant;
import com.atguigu.gulimall.order.dao.OrderDao;
import com.atguigu.gulimall.order.entity.OrderEntity;
import com.atguigu.gulimall.order.entity.OrderItemEntity;
import com.atguigu.gulimall.order.entity.PaymentInfoEntity;
import com.atguigu.gulimall.order.enume.OrderStatusEnum;
import com.atguigu.gulimall.order.feign.CartFeignService;
import com.atguigu.gulimall.order.feign.MemberFeignService;
import com.atguigu.gulimall.order.feign.ProductFeignService;
import com.atguigu.gulimall.order.feign.WmsFeignService;
import com.atguigu.gulimall.order.interceptor.LoginUserInterCeptor;
import com.atguigu.gulimall.order.service.OrderItemService;
import com.atguigu.gulimall.order.service.OrderService;
import com.atguigu.gulimall.order.service.PaymentInfoService;
import com.atguigu.gulimall.order.to.OrderCreateTo;
import com.atguigu.gulimall.order.vo.*;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


@Service("orderService")
public class OrderServiceImpl extends ServiceImpl<OrderDao, OrderEntity> implements OrderService {
    private ThreadLocal<OrderSubmitVo> confimVoThreadLocal = new ThreadLocal<>();

    final OrderItemService orderItemService;
    final MemberFeignService memberFeignService;
    final CartFeignService cartFeignService;
    final ThreadPoolExecutor executor;
    final WmsFeignService wmsFeignService;
    final StringRedisTemplate redisTemplate;

    final ProductFeignService productFeignService;
    final RabbitTemplate rabbitTemplate;
    final PaymentInfoService paymentInfoService;
    final AlipayTemplate template;
    final String CART_PREFIX = "gulimall:cart:";

    public OrderServiceImpl(OrderItemService orderItemService, MemberFeignService memberFeignService,
                            CartFeignService cartFeignService, ThreadPoolExecutor executor,
                            WmsFeignService wmsFeignService, StringRedisTemplate redisTemplate,
                            ProductFeignService productFeignService, RabbitTemplate rabbitTemplate,
                            PaymentInfoService paymentInfoService, AlipayTemplate template) {
        this.orderItemService = orderItemService;
        this.memberFeignService = memberFeignService;
        this.cartFeignService = cartFeignService;
        this.executor = executor;
        this.wmsFeignService = wmsFeignService;
        this.redisTemplate = redisTemplate;
        this.productFeignService = productFeignService;
        this.rabbitTemplate = rabbitTemplate;
        this.paymentInfoService = paymentInfoService;
        this.template = template;
    }

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<OrderEntity> page = this.page(
                new Query<OrderEntity>().getPage(params),
                new QueryWrapper<OrderEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public OrderConfimVo confirmOrder() throws ExecutionException, InterruptedException {
        OrderConfimVo confimVo = new OrderConfimVo();
        MemberRespVo memberRespVo = LoginUserInterCeptor.loginUser.get();
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
            //远程查询所有收货地址
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());
            confimVo.setAddress(address);
        }, executor);
        CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
            //远程购物车所有选中的购物项
            RequestContextHolder.setRequestAttributes(requestAttributes);
            List<OrderItemVo> items = cartFeignService.getCurrentUserCartItmes();
            confimVo.setItems(items);
        }, executor).thenRunAsync(() -> {
            List<Long> collect = confimVo.getItems().stream().map(item -> item.getSkuId()).collect(Collectors.toList());
            R hasStock = wmsFeignService.getSkuHasStock(collect);
            List<SkuStockVo> data = hasStock.getData(new TypeReference<>() {
            });
            if (data != null) {
                Map<Long, Boolean> map = data.stream().collect(Collectors.toMap(SkuStockVo::getSkuId, SkuStockVo::getHasStock));
                confimVo.setStocks(map);
            }
        }, executor);

        //查询用户积分
        Integer integration = memberRespVo.getIntegration();
        confimVo.setIntegration(integration);
        //其他数据自动计算
        CompletableFuture.allOf(getAddressFuture, cartFuture).get();
        //TODO 防重令牌
        String token = UUID.randomUUID().toString().replace("-", "");
        redisTemplate.opsForValue().set(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId(), token, 30, TimeUnit.MINUTES);
        confimVo.setOrderToken(token);
        return confimVo;
    }

    //    @GlobalTransactional
    @Transactional
    @Override
    public SubmitOrderResponesVo submitOrder(OrderSubmitVo vo) {
        confimVoThreadLocal.set(vo);
        //验证令牌【令牌的对比和删除必须保证原子性】
        SubmitOrderResponesVo respones = new SubmitOrderResponesVo();
        //如果使用token机制，就应该使用脚本的方式来对比和删令牌
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        String orderToken = vo.getOrderToken();
        MemberRespVo memberRespVo = LoginUserInterCeptor.loginUser.get();
        respones.setCode(0);
        //原子验证和删除令牌（0 - 令牌失败；1 - 删除成功）
        Long result = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class),
                Arrays.asList(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId()),
                orderToken);
        if (result == 0L) {
            respones.setCode(1);
            return respones;
        } else {
            //防重令牌验证成功，也说明删除令牌成功
            //令牌验证通过就进行创建下单操作：去服务器创建订单,验令牌，验价格，锁库存等操作....
            OrderCreateTo order = createOrder();//创建订单
            //验价
            BigDecimal payAmount = order.getOrder().getPayAmount();
            BigDecimal payPrice = vo.getPayPrice();
            if (Math.abs(payAmount.subtract(payPrice).doubleValue()) < 0.01) {
                //金额对比
                //3。保存订单到数据库
                saveOrder(order);
                //4.库存锁定，只要订单一保存就要锁库存，只要有异常，就回滚订单数据
                //订单号：给哪个订单锁的库存
                //所有订单项（商品的skuid，锁了几件商品，商品名字）
                WareSkuLockVo lockVo = new WareSkuLockVo();
                lockVo.setOrderSn(order.getOrder().getOrderSn()); //为这个订单号锁库存
                List<OrderItemVo> locks = order.getOrderItems().stream().map(item -> {
                    OrderItemVo itemVo = new OrderItemVo();
                    itemVo.setSkuId(item.getSkuId());
                    itemVo.setCount(item.getSkuQuantity());//订单项的数量
                    itemVo.setTitle(item.getSkuName());
                    return itemVo;
                }).collect(Collectors.toList());
                lockVo.setLocks(locks); //锁定的所有订单项数据
                //只要订单保存成功就远程调用库存服务，然后锁库存，防止订单支付成功以后还没有库存
                //TODO 远程锁库存
                R r = wmsFeignService.orderLockStock(lockVo);
                if (r.getCode() == 0) {//锁成功了
                    respones.setOrder(order.getOrder());
                    //TODO 订单创建成功就发送消息给MQ
                    /**
                     * 只要订单一创建成功，就会使用路由键（order.create.order",order）给交换机（order-event-exchange）发送消息
                     * 消息就先会按照路由键（）来到延时队列
                     * 延时一定时间以后，只要过期了，就会以另外一种路由键的方式，这都是自动配置的，然后来到死信队列，
                     * 最后监听死信队列的消费者，就会收到到死信队列里面要关闭的订单数据
                     *
                     * 第一个参数：给哪个交换机发消息
                     * 第二个参数：给这个交换机的谁发消息？（也即路由键）
                     * 第三个参数：发的消息是什么？ 把创建成功的订单数据放到这里
                     */
                    rabbitTemplate.convertAndSend("order-event-exchange", "order.create.order", order.getOrder());
                    return respones;
                } else {
                    //锁定失败了，抛异常回滚事务
                    String msg = (String) r.get("msg");
                    throw new NoStockException(msg);
                }
            } else {
                respones.setCode(2); //金额对比失败
                return respones;
            }
        }
    }

    @Override
    public OrderEntity getOrderByOrderSn(String orderSn) {
        OrderEntity order_sn = this.getOne(new QueryWrapper<OrderEntity>().eq("order_sn", orderSn));
        return order_sn;
    }

    @Override
    public void closeOrder(OrderEntity entity) {
        //关闭订单之前，先来查询这个订单的最新状态
        //因为之前订单创建成功，直接发的是数据库保存的这个对象，所以数据库保存的这个对象是直接有自增id的，所以直接拿到这个订单的id
        OrderEntity orderEntity = this.getById(entity.getId());
        //判断一下当前的状态
        /**
         * 什么情况下才需要关单
         * 只有待付款当中才能关单
         */
        if (orderEntity.getStatus() == OrderStatusEnum.CREATE_NEW.getCode()) {
            //就进行关单（改一个状态）
            OrderEntity update = new OrderEntity();
            update.setId(entity.getId());
            update.setStatus(OrderStatusEnum.CANCLED.getCode());
            this.updateById(update);
            OrderTo orderTo = new OrderTo();
            BeanUtils.copyProperties(orderEntity, orderTo);
            //只要订单释放了，再给订单服务的交换机发一个消息，然后让订单服务的这个交换机与库存绑定的库存释放的队列进行发送消息，告知订单释放成功
            try {
                //TODO 保证消息一定会发送出去 可以每发一个消息，都做好相应的日志记录(给数据保存每一个消息的详细信息)
                //TODO
                rabbitTemplate.convertAndSend("order-event-exchange", "order.release.other", orderTo);
                //调用支付宝进行收单
                ClosePayVo payVo = new ClosePayVo();
                payVo.setOut_trade_no(entity.getOrderSn());
                payVo.setTrade_no("");
                payVo.setOperator_id(UUID.fastUUID().toString(false));
                template.closePay(payVo);
            } catch (Exception e) {
                //TODO 将没发送成功的消息进行重试发送

            }

        }
    }

    @Override
    public PayVo getOrderPay(String orderSn) {
        PayVo payVo = new PayVo();
        OrderEntity order = this.getOrderByOrderSn(orderSn);
        List<OrderItemEntity> orderItems = orderItemService.list(new QueryWrapper<OrderItemEntity>().eq("order_sn",
                order.getOrderSn()));
        BigDecimal bigDecimal = order.getPayAmount().setScale(2, BigDecimal.ROUND_UP);
        payVo.setTotal_amount(bigDecimal);
        payVo.setOut_trade_no(order.getOrderSn());
        payVo.setSubject(orderItems.get(0).getSkuName());
        payVo.setProduct_code("FAST_INSTANT_TRADE_PAY");
        payVo.setBody(orderItems.get(0).getSkuAttrsVals());
//        payVo.setProduct_code("QUICK_WAP_WAY");
        return payVo;
    }

    @Override
    public PageUtils queryPageWithItem(Map<String, Object> params) {
        MemberRespVo memberRespVo = LoginUserInterCeptor.loginUser.get();
        IPage<OrderEntity> page = this.page(
                new Query<OrderEntity>().getPage(params),
                new QueryWrapper<OrderEntity>().eq("member_id", memberRespVo.getId()).orderByDesc("id")
        );
        List<OrderEntity> orderList = page.getRecords().stream().map(order -> {
            List<OrderItemEntity> orderItemEntities = orderItemService.list(new QueryWrapper<OrderItemEntity>().eq(
                    "order_sn",
                    order.getOrderSn()));
            order.setItemEntities(orderItemEntities);
            return order;
        }).collect(Collectors.toList());
        page.setRecords(orderList);
        return new PageUtils(page);
    }

    @Transactional
    @Override
    public String handlePayResult(PayAsyncVo vo, HttpServletRequest request) throws AlipayApiException {
        //验签
        //获取支付宝POST过来反馈信息
        Map<String, String> params = new HashMap<String, String>();
        Map requestParams = request.getParameterMap();
        for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext(); ) {
            String name = (String) iter.next();
            String[] values = (String[]) requestParams.get(name);
            String valueStr = "";
            for (int i = 0; i < values.length; i++) {
                valueStr = (i == values.length - 1) ? valueStr + values[i]
                        : valueStr + values[i] + ",";
            }
            //乱码解决，这段代码在出现乱码时使用。如果mysign和sign不相等也可以使用这段代码转化
            //valueStr = new String(valueStr.getBytes("ISO-8859-1"), "gbk");
            params.put(name, valueStr);
        }
        //计算得出通知验证结果
        boolean verify_result = AlipaySignature.rsaCheckV1(params, template.getAlipay_public_key(), "utf-8", "RSA2");
        if (verify_result) {//验证成功
            System.out.println("签名验证成功");
            savePaymentInfo(vo);
            //请在这里加上商户的业务逻辑程序代码
            order_extracted(vo);
            return "success";
        } else {//验证失败
            System.err.println("签名验证失败");
            return "fail";
        }
    }

    /**
     * 操作订单信息
     *
     * @param vo
     */
    private void order_extracted(PayAsyncVo vo) {
        if (vo.getTrade_status().equals("TRADE_SUCCESS") || vo.getTrade_status().equals("TRADE_FINISHED")) {
            String outTradeNo = vo.getOut_trade_no();
            baseMapper.updateOrderStatus(outTradeNo, OrderStatusEnum.PAYED.getCode());
            OrderEntity order = this.getOrderByOrderSn(outTradeNo);
            if (order != null) {
                //删除购物车中的购物项
                OrderItemEntity orderItem = orderItemService.getOne(new QueryWrapper<OrderItemEntity>().eq(
                        "order_sn",
                        order.getOrderSn()));
                String skuId = orderItem.getSkuId().toString();
                BoundHashOperations<String, Object, Object> cartOps = getCartOps(order.getMemberId());
                String res = (String) cartOps.get(skuId);
                if (StrUtil.isNotEmpty(res)) {
                    cartOps.delete(skuId.toString());
                    System.out.println("删除购物车购物项成功..." + skuId);
                }
            }

        }
    }

    /**
     * 保存流水信息
     *
     * @param vo
     */
    private void savePaymentInfo(PayAsyncVo vo) {
        PaymentInfoEntity entity = new PaymentInfoEntity();
        entity.setAlipayTradeNo(vo.getTrade_no());
        entity.setOrderSn(vo.getOut_trade_no());
        entity.setSubject(vo.getBody());
        entity.setTotalAmount(new BigDecimal(vo.getTotal_amount()));
        entity.setPaymentStatus(vo.getTrade_status());
        entity.setCallbackTime(vo.getGmt_create());
        entity.setCallbackContent(vo.getFund_bill_list());
        paymentInfoService.save(entity);
        System.out.println("保存交易流水成功");
    }


    private void saveOrder(OrderCreateTo order) {
        OrderEntity orderEntity = order.getOrder();
        orderEntity.setModifyTime(new Date());
        this.save(orderEntity);
        List<OrderItemEntity> orderItems = order.getOrderItems();
        orderItemService.saveBatch(orderItems);
    }

    private OrderCreateTo createOrder() {
        OrderCreateTo createTo = new OrderCreateTo();
        //生成订单号
        String orderSn = IdWorker.getTimeId();
        //创建订单
        OrderEntity orderEntity = buildOrder(orderSn);

        //获取所有订单购物项
        List<OrderItemEntity> itemEntities = buildOrderItems(orderSn);
        //3.验价格 计算出订单里面的信息，最终验价
        //参数一：哪个订单，参数二：订单项数据，最终计算来的价格会放在订单的实体对象里面
        computePrice(orderEntity, itemEntities);
        createTo.setOrder(orderEntity);
        createTo.setOrderItems(itemEntities);
        return createTo;
    }

    private OrderEntity buildOrder(String orderSn) {
        MemberRespVo memberRespVo = LoginUserInterCeptor.loginUser.get();
        OrderEntity entity = new OrderEntity();
        entity.setOrderSn(orderSn);
        entity.setMemberId(memberRespVo.getId());
        entity.setStatus(0);
        //获取收货地址
        OrderSubmitVo submitVo = confimVoThreadLocal.get();
        R fare = wmsFeignService.getFare(submitVo.getAddrId());
        if (fare != null) {
            FareVo fareVo = fare.getData(new TypeReference<>() {
            });
            entity.setFreightAmount(fareVo.getFare());
            //设置收货人信息
            entity.setReceiverCity(fareVo.getAddress().getCity());
            entity.setReceiverRegion(fareVo.getAddress().getRegion());
            entity.setReceiverDetailAddress(fareVo.getAddress().getDetailAddress());
            entity.setReceiverName(fareVo.getAddress().getName());
            entity.setReceiverPhone(fareVo.getAddress().getPhone());
            entity.setReceiverProvince(fareVo.getAddress().getProvince());
            entity.setReceiverPostCode(fareVo.getAddress().getPostCode());
        }
        return entity;
    }

    /**
     * 构建所有订单项数据
     *
     * @return
     */
    private List<OrderItemEntity> buildOrderItems(String orderSn) {
        List<OrderItemVo> currentUserCartItmes = cartFeignService.getCurrentUserCartItmes();
        if (currentUserCartItmes != null && currentUserCartItmes.size() > 0) {
            List<OrderItemEntity> itemEntities = currentUserCartItmes.stream().map((cartItem) -> {
                OrderItemEntity itemEntity = buildOrderItem(cartItem);
                itemEntity.setOrderSn(orderSn);
                return itemEntity;
            }).collect(Collectors.toList());
            return itemEntities;
        }
        return null;
    }

    /**
     * 构建某一个订单项
     *
     * @param cartItem
     * @return
     */
    private OrderItemEntity buildOrderItem(OrderItemVo cartItem) {
        OrderItemEntity itemEntity = new OrderItemEntity();
        //1、订单信息：订单号

        //2.商品的spu信息
        Long skuId = cartItem.getSkuId();
        R r = productFeignService.getSpuInfoBySkuId(skuId);
        SpuInfoVo data = r.getData(new TypeReference<SpuInfoVo>() {
        });
        itemEntity.setSpuId(data.getId());
        itemEntity.setSpuName(data.getSpuName());
        itemEntity.setSpuBrand(data.getBrandId().toString());
        itemEntity.setCategoryId(data.getCatalogId());

        //3.商品的sku信息
        itemEntity.setSkuId(cartItem.getSkuId());
        itemEntity.setSkuName(cartItem.getTitle());
        itemEntity.setSkuPic(cartItem.getImage());
        itemEntity.setSkuPrice(cartItem.getPrice());
        String skuAttr = StringUtils.collectionToDelimitedString(cartItem.getSkuAttr(), ";");
        itemEntity.setSkuAttrsVals(skuAttr);
        itemEntity.setSkuQuantity(cartItem.getCount());
        //4.优惠信息【忽略不做】

        //5.积分信息
        itemEntity.setGiftGrowth(cartItem.getPrice().multiply(new BigDecimal(cartItem.getCount().toString())).intValue());
        itemEntity.setGiftIntegration(cartItem.getPrice().multiply(new BigDecimal(cartItem.getCount().toString())).intValue());

        //6.订单项的价格相关信息
        //将每一个订单项的总价格计算出来
        itemEntity.setPromotionAmount(new BigDecimal("0"));
        itemEntity.setCouponAmount(new BigDecimal("0"));
        itemEntity.setIntegrationAmount(new BigDecimal("0"));
        //当前订单项的实际金额计算，总价乘以数量
        BigDecimal orign = itemEntity.getSkuPrice().multiply(new BigDecimal(itemEntity.getSkuQuantity().toString()));
        //总额减去各种优惠
        BigDecimal subtract = orign.subtract(itemEntity.getCouponAmount()).subtract(itemEntity.getPromotionAmount()).subtract(itemEntity.getIntegrationAmount());
        itemEntity.setRealAmount(subtract);
        return itemEntity;
    }

    /**
     * 计算价格 验价格
     *
     * @param orderEntity
     * @param itemEntities
     */
    private void computePrice(OrderEntity orderEntity, List<OrderItemEntity> itemEntities) {
        BigDecimal total = new BigDecimal("0.0");//默认的总价格
        BigDecimal coupon = new BigDecimal("0.0");
        BigDecimal integration = new BigDecimal("0.0");
        BigDecimal promotion = new BigDecimal("0.0");

        BigDecimal gift = new BigDecimal("0.0");
        BigDecimal growth = new BigDecimal("0.0");
        //订单的总额,叠加每一个订单项的总额信息，也即所有购物项相加的总额
        for (OrderItemEntity entity : itemEntities) {
            coupon = coupon.add(entity.getCouponAmount());
            integration = integration.add(entity.getIntegrationAmount());
            promotion = promotion.add(entity.getPromotionAmount());
            total = total.add(entity.getRealAmount());
            //当前订单能获得的积分信息
            gift = gift.add(new BigDecimal(entity.getGiftIntegration().toString()));
            growth = growth.add(new BigDecimal(entity.getGiftGrowth().toString()));
            //计算每一项的价格：单价乘以数量
            // BigDecimal decimal = entity.getSkuPrice().multiply(new BigDecimal(entity.getSkuQuantity().toString()));
        }
        //1.订单价格相关
        orderEntity.setTotalAmount(total);
        //设置应付总额
        orderEntity.setPayAmount(total.add(orderEntity.getFreightAmount()));
        orderEntity.setPromotionAmount(promotion);
        orderEntity.setIntegrationAmount(integration);
        orderEntity.setCouponAmount(coupon);
        //设置积分等信息
        orderEntity.setIntegration(gift.intValue());
        orderEntity.setGrowth(growth.intValue());

        orderEntity.setDeleteStatus(0); //0代表未删除状态
    }

    private BoundHashOperations<String, Object, Object> getCartOps(Long memberId) {
        String cartKey = "";
        if (!Objects.isNull(memberId)) {
            cartKey = CART_PREFIX + memberId;
        }
        BoundHashOperations<String, Object, Object> operations = redisTemplate.boundHashOps(cartKey);
        return operations;
    }

}